<?php

namespace App\Helpers;

/**
 * RSA签名/验签帮助类
 * @package App\Helpers
 */
class RsaHelper {

    /**
     * RSA签名
     * @param $data 签名数据(需要先排序，然后拼接)
     * @param $priKey 签名用商户私钥
     * @param string $signType 签名方式
     * @return Sign签名
     */
    function sign($data, $priKey, $signType = "RSA2") {
        //获取openssl密钥
        $res = $this->privateKey($priKey);

        $sign = "";
        //调用openssl内置签名方法，生成签名$sign
        if ($signType == "RSA2") {
            openssl_sign($data, $sign, $res, OPENSSL_ALGO_SHA256);
        } else {
            openssl_sign($data, $sign, $res);
        }

        //最后的签名，需要用base64编码
        $sign = base64_encode($sign);

        return $sign;
    }

    /**
     * RSA验签
     * @param $data 待签名数据(需要先排序，然后拼接)
     * @param $sign  需要验签的签名,需要base64_decode解码
     * @param $pubKey  签名商户公钥
     * @param string $signType 签名方式
     * @return 验签是否通过 bool值
     */
    function verify($data, $sign, $pubKey, $signType = "RSA2") {

        //转换为openssl格式密钥
        $res = $this->publicKey($pubKey);

        //调用openssl内置方法验签，返回bool值
        if ($signType == "RSA2") {
            $result = (bool)openssl_verify($data, base64_decode($sign), $res, OPENSSL_ALGO_SHA256);
        } else {
            $result = (bool)openssl_verify($data, base64_decode($sign), $res);
        }

        //返回资源是否成功
        return $result;
    }

    /**
     * 获取签名内容
     * @param $params 签名参数
     * @return string
     */
    function getSignContent($params) {
        ksort($params);

        $stringToBeSigned = "";
        foreach ($params as $k => $v) {
            if (false === $this->checkEmpty($v) && "@" != substr($v, 0, 1)) {
                $stringToBeSigned .= "{$k}={$v}&";
            }
        }

        unset ($k, $v);
        return rtrim($stringToBeSigned, "&");
    }

    /**
     * 校验$value是否非空
     * @param $value
     * @return boolean
     **/
    protected function checkEmpty($value) {
        if (!isset($value))
            return true;
        if ($value === null)
            return true;
        if (trim($value) === "")
            return true;

        return false;
    }


    /**
     * 公钥加密方法
     * @param string $data 要加密的数据
     * @param string $pubKey 公钥
     * @return string
     */
    function encrypt($data, $pubKey) {
        //转换为openssl格式密钥
        $res = $this->publicKey($pubKey);
        $blocks = $this->splitCN($data, 0, 30);
        $encrypt  = "";
        $encodes  = array();
        foreach ($blocks as $n => $block) {
            openssl_public_encrypt($block, $encrypt, $res);
            $encodes[] = base64_encode($encrypt);
        }
        $encrypt = implode(",", $encodes);
        return $encrypt;
    }

    /**
     * 私钥解密方法
     * @param string $encrypt 已加密的数据
     * @param string $priKey 私钥
     * @return string
     */
    function decrypt($encrypt, $priKey) {
        //获取openssl密钥
        $res = $this->privateKey($priKey);
        $decodes = explode(',', $encrypt);
        $decrypt = "";
        $dcyCont = "";
        foreach ($decodes as $n => $decode) {
            $decode = base64_decode($decode);
            openssl_private_decrypt($decode, $dcyCont, $res);
            $decrypt .= $dcyCont;
        }
        return $decrypt;
    }

    /**
     * 处理公钥
     * @param $publicKey 公钥
     * @return mixed
     */
    protected function publicKey($publicKey) {
        return "-----BEGIN PUBLIC KEY-----\n" .
            wordwrap($publicKey, 64, "\n", true) .
            "\n-----END PUBLIC KEY-----";
    }

    /**
     * 处理私钥
     * @param $privateKey 私钥
     * @return mixed
     */
    protected function privateKey($privateKey) {
        return "-----BEGIN RSA PRIVATE KEY-----\n" .
            wordwrap($privateKey, 64, "\n", true) .
            "\n-----END RSA PRIVATE KEY-----";
    }

    protected function splitCN($cont, $n = 0, $subnum, $charset = "utf-8") {
        //$len = strlen($cont) / 3;
        $arrr = array();
        for ($i = $n; $i < strlen($cont); $i += $subnum) {
            $res = $this->subCNchar($cont, $i, $subnum, $charset);
            if (!empty ($res)) {
                $arrr[] = $res;
            }
        }

        return $arrr;
    }

    protected function subCNchar($str, $start = 0, $length, $charset = "utf-8") {
        if (strlen($str) <= $length) {
            return $str;
        }
        $re['utf-8'] = "/[\x01-\x7f]|[\xc2-\xdf][\x80-\xbf]|[\xe0-\xef][\x80-\xbf]{2}|[\xf0-\xff][\x80-\xbf]{3}/";
        $re['gb2312'] = "/[\x01-\x7f]|[\xb0-\xf7][\xa0-\xfe]/";
        $re['gbk'] = "/[\x01-\x7f]|[\x81-\xfe][\x40-\xfe]/";
        $re['big5'] = "/[\x01-\x7f]|[\x81-\xfe]([\x40-\x7e]|\xa1-\xfe])/";
        preg_match_all($re[$charset], $str, $match);
        $slice = join("", array_slice($match[0], $start, $length));
        return $slice;
    }
}